package biback.domain.account

import biback.domain._
import biback.domain.account.BalanceType.AccountBalance
import biback.domain.customer._
import biback.domain.product.Limits._
import biback.domain.product._
import biback.utils.DomainObject
import cats.data.NonEmptyMap
import cats.data.NonEmptyMap._

/**
  * 账户。
  * @param id 账户标识，一旦产生永不改变。
  * @param no 账号
  * @param openBrcNo 开户机构号
  * @param status 账户状态
  * @param subs 子账户
  * @param idValidated 已完成的身份验证
  * @param boundCards 已绑定的卡
  * @param relatedCards 关联的卡（比如主副卡）
  */
case class Account
(
  id: AccountId,
  no: AccountNo,
  openBrcNo: BranchNo,
  status: AccountStatus,
  subs: NonEmptyMap[SubAccountNo, SubAccount],
  idValidated: IdValidationType = IdValidationType.NoValidated,
  boundCards: List[CardNo] = List.empty[CardNo],
  relatedCards: List[Card] = List.empty[Card]
) extends DomainObject {
  def foundSub(no: SubAccountNo): Either[SubAcctNotFound, SubAccount] =
    subs(no).toRight(SubAcctNotFound(no))

  def onEvent(e: SubAccountOpened): Account =
    copy(subs = subs.add(Account.createSub(e)))

  def onEvent(e: BalanceChanged): Account = {
    val s = subs(e.subAcctNo).get
    e.balances.toSortedMap.foreach(b => assert(s.balances(b._1).get == b._2))
    val updated = s.balances ++ e.balances.map(_ + e.amount)
    copy(subs = subs.updateWith(e.subAcctNo)(_.copy(balances = updated)))
  }
}

object Account {
  def apply(e: AccountOpened): Account = {
    val se = SubAccountOpened(e.ctx, e.acctId, e.subAcctNo, e.product, e.quotaBrcNo)
    val p = NonEmptyMap.of(createSub(se))
    Account(e.acctId, e.acctNo, e.quotaBrcNo, AccountStatus.Normal, p)
  }

  def createSub(e: SubAccountOpened): (SubAccountNo, SubAccount) = {
    val b = NonEmptyMap.of[BalanceType, BiDec](AccountBalance -> BiDec0)
    (e.subAcctNo, SubAccount(e.product, b, e.ctx.date, e.quotaBrcNo))
  }
}

/**
  * 子账户。
  * @param product 产品
  * @param balances 余额组件
  * @param startIntDay 起息日
  * @param quotaBrcNo 考核机构号（即该机构营销的产品）
  * @param lastIntDay 上次结息日
  * @param trnAmtSums 交易金额累计（业务控制用）
  */
case class SubAccount
(
  product: SaleableProduct,
  balances: NonEmptyMap[BalanceType, BiDec],
  startIntDay: TrnDate,
  quotaBrcNo: BranchNo,
  lastIntDay: Option[TrnDate] = None,
  trnAmtSums: Map[LimitKey, BiDec] = Map.empty[LimitKey, BiDec]
) extends DomainObject {
  def +=(t: BalanceType, ts: BalanceType*): ErrOr[SubAccount] = {
    val types = List(t) ++ ts.toList
    val founds = balances.toSortedMap.filterKeys(types.contains)
    val diffs = types.diff(founds.keys.toList)
    for {
      _ <- diffs.isEmpty ?= BalanceTypeNotFound(diffs)
    } yield copy(balances = NonEmptyMap(founds.head, founds.tail))
  }
  val calcIntFrom = lastIntDay.getOrElse(startIntDay)
}
